package com.google.code.mochaccino.framework.crypto.pgp.impl;

/*
 * Copyright 2012 Claude Houle claude.houle@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.google.code.mochaccino.framework.crypto.EncryptionException;
import com.google.code.mochaccino.framework.crypto.EncryptionProvider;
import com.google.code.mochaccino.framework.crypto.pgp.types.PgpPublicKey;
import com.google.code.mochaccino.framework.crypto.pgp.types.PgpSymmetricKey;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Iterator;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;

/** The PGP Block Generator */
public class PgpKeyProviderGenerator implements PgpKeyProvider {

	/** The PGP Private Key */
	private PGPPrivateKey privateKey = null;

	/** The PGP Public Key */
	private PGPPublicKey publicKey = null;

	/** The Public Key Ring */
	private PGPPublicKeyRing publicKeyRing = null;

	/** The Armored PGP Public Key */
	private String publicKeyRingBlock = null;

	/** The Secret Key Ring */
	private PGPSecretKeyRing secretKeyRing = null;

	/** The Armored PGP Secret Key */
	private String secretKeyRingBlock = null;

	/**
	 * @param masterKeyAlgo
	 * @param masterKeySize
	 * @param subKeyAlgo
	 * @param subKeySize
	 * @param encKeyAlgo
	 * @param identity
	 * @param passphrase
	 *
	 * @throws EncryptionException
	 */
	public PgpKeyProviderGenerator( PgpPublicKey masterKeyAlgo, int masterKeySize, PgpPublicKey subKeyAlgo, int subKeySize, PgpSymmetricKey encKeyAlgo, String identity, String passphrase ) throws EncryptionException {
		try {
			KeyPairGenerator maskerKeyPairGenerator = KeyPairGenerator.getInstance( masterKeyAlgo.getAlgoName(), EncryptionProvider.getBouncyCastleName() );
			maskerKeyPairGenerator.initialize( masterKeySize );
			PGPKeyPair masterKeyPair = new PGPKeyPair( masterKeyAlgo.getAlgo(), maskerKeyPairGenerator.generateKeyPair(), new Date() );
			KeyPairGenerator subKeyPairGenerator = KeyPairGenerator.getInstance( subKeyAlgo.getAlgoName(), EncryptionProvider.getBouncyCastleName() );
			subKeyPairGenerator.initialize( subKeySize );
			PGPKeyPair subKeyPair = new PGPKeyPair( subKeyAlgo.getAlgo(), subKeyPairGenerator.generateKeyPair(), new Date() );
			PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator( PGPSignature.POSITIVE_CERTIFICATION,
			                                                          masterKeyPair,
			                                                          identity,
			                                                          encKeyAlgo.getAlgo(),
			                                                          passphrase.toCharArray(),
			                                                          true,
			                                                          null,
			                                                          null,
			                                                          new SecureRandom(),
			                                                          EncryptionProvider.getBouncyCastleName() );
			keyRingGen.addSubKey( subKeyPair );
			secretKeyRing = keyRingGen.generateSecretKeyRing();
			secretKeyRingBlock = export( secretKeyRing );
			publicKeyRing = keyRingGen.generatePublicKeyRing();
			publicKeyRingBlock = export( publicKeyRing );
			// Extract Private SubKey
			Iterator<?> it = secretKeyRing.getSecretKeys();
			while ( it.hasNext() ) {
				PGPSecretKey sk = (PGPSecretKey) it.next();
				if ( sk.getPublicKey().isEncryptionKey() ) {
					privateKey = sk.extractPrivateKey( passphrase.toCharArray(), EncryptionProvider.getBouncyCastleName() );
					publicKey = sk.getPublicKey();
					break;
				}
			}
		} catch ( PGPException e ) {
			throw new EncryptionException( e );
		} catch ( NoSuchProviderException e ) {
			throw new EncryptionException( e );
		} catch ( NoSuchAlgorithmException e ) {
			throw new EncryptionException( e );
		} catch ( IOException e ) {
			throw new EncryptionException( e );
		}
	}

	@Override
	public PGPPrivateKey getPrivateKey() {
		return privateKey;
	}

	@Override
	public String getPrivateKeyBlock() {
		return secretKeyRingBlock;
	}

	@Override
	public PGPPublicKey getPublicKey() {
		return publicKey;
	}

	@Override
	public String getPublicKeyBlock() {
		return publicKeyRingBlock;
	}

	/** Export Public Key to Armored Format */
	private String export( PGPPublicKeyRing ring ) throws IOException {
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		ArmoredOutputStream armor = new ArmoredOutputStream( bytes );
		ring.encode( armor );
		IOUtils.closeQuietly( armor );
		IOUtils.closeQuietly( bytes );
		return new String( bytes.toByteArray() );
	}

	/** Export Secret Key to Armored Format */
	private String export( PGPSecretKeyRing ring ) throws IOException {
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		ArmoredOutputStream armor = new ArmoredOutputStream( bytes );
		ring.encode( armor );
		IOUtils.closeQuietly( armor );
		IOUtils.closeQuietly( bytes );
		return new String( bytes.toByteArray() );
	}
}
